#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <fcntl.h>
#include <cpuid.h>
#include <errno.h>
#include "random_in.h"
#include "sm3.h"

#define ENTROPY_SOURCE_SYS "/proc/softirqs"
#define ENTROPY_SOURCE_DEV "/dev/urandom"

/**
 * @brief 检测是否支持指定文件
 *
 * @return int 不支持返回1， 支持返回0.
 */
static int is_support_file(const char* file)
{
    int ret = 0;
    ret     = access(file, F_OK | R_OK);
    if (ret) return errno;
    return ret;
}

/**
 * @brief Linux文件锁，上锁
 * 
 * @param fd 
 * @return int 
 */
int lockfile(int fd)
{
    return flock(fd, LOCK_EX|LOCK_NB);
}

/**
 * @brief Linux文件锁，解锁
 * 
 * @param fd 
 * @return int 
 */
int unlockfile(int fd)
{
    return flock(fd, LOCK_UN);
}

/* ------ system entropy source ------ */

static int _update_entropy_sys()
{
    int nread = 0;
    int fd = 0;
    unsigned char buf[SYS_ENTROPY_LEN] = {0};
    unsigned char hash[SM3_DATA_LEN] = {0};
    if (is_support_file(ENTROPY_SOURCE_SYS)) return ENODEV;

    fd = open(ENTROPY_SOURCE_SYS, O_RDONLY);
    if (fd < 0) return errno;

    // int ret = lockfile(fd);
    // if(ret < 0){
    //     close(fd);
    //     return errno;
    // }
    nread = read(fd, buf, SYS_ENTROPY_LEN);
    if(nread < 1) return errno;
    // unlockfile(fd);

    sm3(buf, nread, hash);
    entropy_pool_update((uint32_t*)hash, SM3_DATA_LEN/4);
    close(fd);
    return 0;
}

static int _read_entropy_sys(unsigned char random[number_of_bytes_to_return])
{
    int ret = 0;
    ret = sm3_df(get_entropy_pool(), ENTROPY_POOL_LEN_BYTES, random);
    if(ret) return ret;
    entropy_pool_update((uint32_t*)random, number_of_bytes_to_return/4);
    return ret;
}

/**
 * @brief 读取系统熵值
 * 
 * @param random [out]熵值
 * @param len 长度
 * @param flag 指示是否更新熵池，0不更新，其他值更新
 * @return int 
 */
int read_entropy_sys(unsigned char* random, int len)
{
    int ret = 0;
    unsigned char entropy[number_of_bytes_to_return];
    int loop = len / number_of_bits_to_return;
    int left = len - loop*number_of_bytes_to_return;

    if(random_state_is(RANDOM_STATE_INIT) ||
        sys_entropy_counter_arrived()) {
        ret = _update_entropy_sys();
        sys_entropy_counter_reset();
        if(ret) return ret;
    }

    for (int i = 0; i < loop; i++) {
        ret = _read_entropy_sys(entropy);
        if(ret) goto out;
        memcpy(random+i*loop, entropy, number_of_bytes_to_return);
    }
    if(left > 0){
        ret = _read_entropy_sys(entropy);
        if(ret) goto out;
        memcpy(random+number_of_bytes_to_return*loop, entropy, left);
    }

    sys_entropy_counter_add();
out:
    return ret;
}

/* ------ cpu rng ------ */

#ifndef bit_RDRND
#define bit_RDRND 0x40000000
#endif

/**
 * @brief 检测cpu是否支持rdrand指令
 *
 * @return int 不支持返回1， 支持返回0.
 */
static int is_support_cpu()
{
    uint32_t eax = 0, ebx = 0, ecx = 0, edx = 0;
    __get_cpuid(1, &eax, &ebx, &ecx, &edx);
    return !(ecx & bit_RDRND);
}

/**
 * @brief 生成随机数
 *
 * @param random 随机数
 * @return int 成功返回0，失败返回非0.
 */
static int random_rdrand(uint64_t* random)
{
    uint64_t foo             = 0;
    int      cf_error_status = 0;
    if (!random) return EINVAL;
    __asm__ volatile(
        "\n\
            rdrand %%rax;\n\
            mov $1,%%edx;\n\
            cmovae %%rax,%%rdx;\n\
            mov %%edx,%1;\n\
            mov %%rax, %0;"
        : "=r"(foo), "=r"(cf_error_status)::"%rax", "%rdx");
    *random = foo;
    return !cf_error_status;
}

int read_entropy_cpu(unsigned char* random, const int len)
{
    if (random == NULL || len < 1) return -1;
    uint64_t type_len   = sizeof(len);
    uint64_t loop       = len / type_len;
    uint64_t left       = len - loop * type_len;
    uint64_t random_gen = 0;
    uint64_t i;

    if (is_support_cpu()) {
        return ENODEV;
    }
    for (i = 0; i < loop; i++) {
        if (!random_rdrand(&random_gen)) {
            memcpy(random + i * type_len, &random_gen, type_len);
        } else {
            return EIO;
        }
    }
    if (left) {
        if (!random_rdrand(&random_gen))
            memcpy(random + type_len * loop, &random_gen, left);
        else
            return EIO;
    }
    return 0;
}

/* ------ /dev/random ------ */

int read_entropy_dev(unsigned char* random, const int len)
{
    int fd = 0;
    int nread = 0;
    if (random == NULL || len <= 0) return EINVAL;

    if (is_support_file(ENTROPY_SOURCE_DEV)) return ENODEV;

    fd = open(ENTROPY_SOURCE_DEV, O_RDONLY);
    if (fd < 0) return errno;
    nread = read(fd, random, len);
    close(fd);

    if (nread != len) return EAGAIN;
    return 0;
}
